package my_interface

import (
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"log"
	"time"

	"github.com/tarm/serial"
)

var (
	SerilPort   *serial.Port
	buf              = make(chan byte, 4096)
	receiveFlag bool = false
	sendFlag    bool = false
)

const (
	START byte = iota + 1
	PICTURE_INFO
	PICTURE_DATA
	END
)

func serialOpen(name string, buad int) (s *serial.Port, err error) {
	//设置串口编号
	c := &serial.Config{Name: name, Baud: buad}

	//打开串口
	s, err = serial.OpenPort(c)
	return
}

func sendSeialCmd(s *serial.Port, cmd byte) {
	var (
		b   []byte
		crc uint32
	)
	b = append(b, 0x55) //头码
	b = append(b, 0x55) //头码
	b = append(b, 0)    //数据长度高位
	b = append(b, 5)    //数据长度低位
	b = append(b, cmd)  //指令
	crc = crc32.ChecksumIEEE(b[4:])
	b = append(b, byte(crc>>24))
	b = append(b, byte(crc>>16))
	b = append(b, byte(crc>>8))
	b = append(b, byte(crc>>0))

	log.Println(b)
	s.Write(b)
}

func sendSeialData(s *serial.Port, cmd byte, data []byte) {
	var (
		b   []byte
		crc uint32
	)
	b = append(b, 0x55) //头码
	b = append(b, 0x55) //头码
	b = append(b, 0)    //数据长度高位
	b = append(b, 0)    //数据长度低位
	b = append(b, cmd)  //指令

	for i := 0; i < len(data); i++ {
		b = append(b, data[i])
	}

	crc = crc32.ChecksumIEEE(b[4:])
	b = append(b, byte(crc>>24))
	b = append(b, byte(crc>>16))
	b = append(b, byte(crc>>8))
	b = append(b, byte(crc>>0))

	b[2] = byte((len(b) - 4) >> 8)
	b[3] = byte((len(b) - 4) >> 0)

	log.Println(b)
	s.Write(b)
}

func perInserSlice(i byte, s []byte) []byte {
	res := append([]byte{i}, s...)
	return res
}

func data_analyse(b []byte) {
	if b[0] == 11 {
		sendFlag = true
	} else if b[0] == 22 {
		receiveFlag = true
	}
}

func dataProcess(s *serial.Port, buf chan byte) {
	var (
		length     int
		flag       bool = false
		timeoutCnt int
		b          []byte
		crcB       []byte
	)

	//串口读取函数
	go func() {
		recBuffer := make([]byte, 4096)
		for {
			n, err := s.Read(recBuffer)
			// fmt.Print(string(recBuffer[:n]))
			if n > 0 {
				for i := 0; i < n; i++ {
					buf <- recBuffer[i]
				}
				n = 0
			}
			if err != nil {
				if err != io.EOF {
					log.Println(err)
				}
				break
			}
		}
	}()

	go func() {
		for {
			//超时判断
			timeoutCnt++
			if (length-1) > len(buf) && timeoutCnt >= 50 {
				flag = false
			}
			time.Sleep(time.Millisecond)
		}
	}()

	go func() {
		for {
			if flag {
				//串口数据解析
				if len(buf) >= length {
					flag = false

					b = b[0:0]
					crcB = crcB[0:0]
					for i := 0; i < (length - 4); i++ {
						b = append(b, <-buf)
					}
					for i := (length - 4); i < length; i++ {
						crcB = append(crcB, <-buf)
					}

					c := uint32(crcB[0])<<24 | uint32(crcB[1])<<16 | uint32(crcB[2])<<8 | uint32(crcB[3])<<0
					crc := crc32.ChecksumIEEE(b)
					if crc == c {
						data_analyse(b)
						flag = false
					} else {
						fmt.Println("crc error")
					}
				}
			} else {
				if <-buf == 0x55 {
					if <-buf == 0x55 {
						lengthH := int(<-buf)
						lengthL := int(<-buf)
						length = lengthH<<8 | lengthL
						flag = true
						timeoutCnt = 0
					}
				}
			}
		}
	}()
}

func SerialStart(name string, buad int, bootPath string) {
	var filenale string
	SerilPort, err := serialOpen(name, buad)
	if err != nil {
		fmt.Println("串口打开错误!")
	}
	dataProcess(SerilPort, buf)
	fmt.Println(bootPath)
	for {
		if sendFlag {
			sendFlag = false
			//发送开始更新指令
			sendSeialCmd(SerilPort, START)
			//准备读取bin文件
			if bootPath != "" {
				filenale = bootPath
			} else {
				filenale = "project.bin"
			}

			b, err := ioutil.ReadFile(filenale)
			if err != nil {
				fmt.Print(err)
			}

			bInfo := make([]byte, 0)
			l := len(b)
			bInfo = append(bInfo, byte(l>>24))
			bInfo = append(bInfo, byte(l>>16))
			bInfo = append(bInfo, byte(l>>8))
			bInfo = append(bInfo, byte(l>>0))
			sendSeialData(SerilPort, PICTURE_INFO, bInfo) //发送bin长度

			percent := l / 1024
			for i := 0; i < l/1024; i++ {
				for {
					if receiveFlag {
						receiveFlag = false
						fmt.Print(i*100/percent, "% ")
						sendSeialData(SerilPort, PICTURE_DATA, b[i*1024:(i+1)*1024])
						break
					}
				}
			}
			if l%1024 > 0 {
				for {
					if receiveFlag {
						receiveFlag = false
						fmt.Println("end")
						sendSeialData(SerilPort, PICTURE_DATA, b[(l/1024)*1024:])
						break
					}
				}
			}
			sendSeialCmd(SerilPort, END)
			break
		}
		time.Sleep(time.Millisecond * 10)
	}
}
